use redis::{ConnectionInfo, ConnectionAddr, Client, RedisError, RedisResult, ErrorKind, AsyncCommands};
use redis::aio::Connection;
use std::error::Error;
use rand::Rng;
use chrono::prelude::*;
use prost::alloc::sync::Arc;

const LOCK_INVENTORY:&'static str = "LOCK_INVENTORY";
const RWLOCK:&'static str = "RWLOCK";
//ARGV[1] 请求命令，read:读锁，write:写锁,unread:释放写锁，unwrite:释放读锁
//ARGV[2]:request_id  ARGV[3]:当前时间戳 ARGV[4] 超时时间s
const SUB_INVENTORY_LUA_SCRIPT:&'static str = r#"
    if redis.call("exists",KEYS[1]) == 0 then
        redis.call("hset",KEYS[1],"reader",0,"writer",0,"rtimeout",0,"wtimeout",0)
    end
    local lock = redis.call("hmget",KEYS[1],"reader","writer","rtimeout","wtimeout")
    if ARGV[1] == "read" then
        if tonumber(lock[2]) == 0 or tonumber(lock[4]) < tonumber(ARGV[3]) then
            return redis.call("hmset",KEYS[1],"reader",tonumber(lock[1])+1,"rtimeout",tonumber(ARGV[3])+tonumber(ARGV[4]))
        end
    elseif ARGV[1] == "write" then
        if tonumber(lock[2]) == 0 or tonumber(lock[4]) < tonumber(ARGV[3]) or lock[2] == ARGV[2] then
            redis.call("hmset",KEYS[1],"writer",ARGV[2],"wtimeout",tonumber(ARGV[3])+tonumber(ARGV[4]))
            if tonumber(lock[1]) == 0 or tonumber(lock[3]) < tonumber(ARGV[3]) then
                return true
            end
        end
    elseif ARGV[1] == "unread" then
        return redis.call("hincrby",KEYS[1],"reader",-1)
    elseif ARGV[1] == "unwrite" then
        if lock[2] == ARGV[2] then
            return redis.call("hMset",KEYS[1],"reader",0,"writer",0)
        end
    end
    return false
"#;


#[tokio::main]
async fn main(){
    let client = Client::open("redis://123.57.130.20/1").expect("连接redis失败");
    let mut sconn = client.get_tokio_connection().await.unwrap();
    let mut gconn = client.get_tokio_connection().await.unwrap();
    tokio::spawn( set_index(sconn));
    tokio::spawn(get_index(gconn));
    tokio::time::sleep(tokio::time::Duration::from_secs(21)).await;
}
async fn set_index(mut conn:Connection) {
    for i in 0..20 {
        let rid = rand::thread_rng().gen_range(100000..999999);
        if write(rid,&mut conn).await.unwrap() {
            wd_log::log_info_ln!("开始写锁");
            let index:Option<i32> = conn.get("index").await.unwrap();
            if let Some(index) = index{
                let _:() = conn.incr("index",1).await.unwrap();
            }else{
                let _:() = conn.set("index",1).await.unwrap();
            }
            tokio::time::sleep(tokio::time::Duration::from_secs(3)).await;
            wd_log::log_info_ln!("释放写锁");
            un_write(rid,&mut conn).await;
        }
        tokio::time::sleep(tokio::time::Duration::from_secs(3)).await;
    }
}
async fn get_index(mut conn:Connection) {
    for i in 0..20 {
        tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
        let rid = rand::thread_rng().gen_range(100000..999999);
        if read(rid,&mut conn).await.unwrap() {
            wd_log::log_info_ln!("进入读锁....");
            let index:Option<i32> = conn.get("index").await.unwrap();
            if let Some(index) = index {
                wd_log::log_info_ln!("index:{}",index);
            }
            un_read(rid,&mut conn).await.unwrap();
        }
    }
}

async fn read(request_id:i32,conn:&mut Connection)->Result<bool,Box<dyn Error+ 'static>>{
    let ts = Utc::now().timestamp();
    let res = redis::cmd("eval").arg(SUB_INVENTORY_LUA_SCRIPT).arg(1).arg(RWLOCK)
        .arg("read").arg(request_id).arg(ts).arg(10).query_async::<_,Option<String>>(conn).await?;
    if res.is_none() {
        return Ok(false)
    }
    Ok(true)
}
async fn un_read(request_id:i32,conn:&mut Connection)->Result<(),Box<dyn Error+ 'static>>{
    redis::cmd("eval").arg(SUB_INVENTORY_LUA_SCRIPT).arg(1).arg(RWLOCK)
        .arg("unread").arg(request_id).arg(0).arg(10).query_async(conn).await?;Ok(())
}
async fn un_write(request_id:i32,conn:&mut Connection)->Result<(),Box<dyn Error+ 'static>>{
    redis::cmd("eval").arg(SUB_INVENTORY_LUA_SCRIPT).arg(1).arg(RWLOCK)
        .arg("unwrite").arg(request_id).arg(0).arg(10).query_async(conn).await?;Ok(())
}
async fn write(request_id:i32,conn:&mut Connection)->Result<bool,Box<dyn Error+ 'static>>{
    let ts = Utc::now().timestamp();
    let res = redis::cmd("eval").arg(SUB_INVENTORY_LUA_SCRIPT).arg(1).arg(RWLOCK)
        .arg("write").arg(request_id).arg(ts).arg(30).query_async::<_,Option<i32>>(conn).await?;
    if res.is_none() {
        return Ok(false)
    }
    Ok(true)
}

